package br.com.yahoo.mau_mss.tutorlucene.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
import org.apache.lucene.search.highlight.TokenSources;
import org.apache.lucene.util.Version;
import org.springframework.stereotype.Service;
import br.com.yahoo.mau_mss.tutorlucene.model.Curriculum;
import br.com.yahoo.mau_mss.tutorlucene.model.SearchResult;
import br.com.yahoo.mau_mss.tutorlucene.service.CurriculumException;
import br.com.yahoo.mau_mss.tutorlucene.service.SearchService;
import br.com.yahoo.mau_mss.tutorlucene.util.Constants;

/**
 * Implementação da interface {@link SearchService}.
 * @author Paulo Sigrist / Wilson A. Higashino
 */
@Service("searchServiceBean")
public class SearchServiceBean extends LuceneBean implements
    SearchService, Serializable {
  private static final long serialVersionUID = 2946540838935661231L;

  /**
   * {@inheritDoc}
   * @see #executeQuery(Query)
   * @see Constants#FIELD_NAME
   */
  @Override
  public List<SearchResult> findCVByName(String name)
      throws CurriculumException {
    // TermQuery faz uma busca em um campo específico do índice
    Query query = new TermQuery(new Term(Constants.FIELD_NAME, name));
    return executeQuery(query, Constants.FIELD_NAME);
  }

  /**
   * {@inheritDoc}
   * @see #executeQuery(Query)
   * @see Constants#FIELD_CONTENT
   */
  @Override
  public List<SearchResult> findCVByContent(String content)
      throws CurriculumException {
    Query query = null;
    try {
      // queryParser permite o uso de operadores AND, +, NOT, -, " ", *, ?, etc.
      // existe também a BooleanQuery para isso
      query = new QueryParser(Version.LUCENE_35, Constants.FIELD_CONTENT, this.analyzer)
          .parse(content);
    } catch (ParseException pex) {
      throw new CurriculumException("Erro no parse da consulta.", pex);
    }

    return executeQuery(query, Constants.FIELD_CONTENT);
  }

  /**
   * Executa a busca a partir do objeto Query passado como parâmetro.
   * 
   * @param query - Consulta a ser executada
   * @param field - O nome do campo onde a busca está sendo feita
   * @throws CurriculumException
   * @see #findCVByContent(String)
   * @see #findCVByName(String)
   * @see #loadCurriculum(File)
   * @see Query
   * @see QueryParser
   * @see IndexReader
   * @see IndexSearcher
   * @see TopScoreDocCollector
   * @see ScoreDoc
   * @see Document
   */
  private List<SearchResult> executeQuery(Query query, String field)
      throws CurriculumException {
    List<SearchResult> results = new ArrayList<SearchResult>();
    try {
      // Quantidade de hits por página
      int hitsPerPage = 30;
      // Abre o índice e cria um IndexSearcher
      IndexReader reader = IndexReader.open(this.directory);
      IndexSearcher searcher = new IndexSearcher(reader);
      // Executa a busca
      TopDocs topDocs = searcher.search(query, hitsPerPage);
      ScoreDoc[] hits = topDocs.scoreDocs;
      Highlighter highlighter = criarHighlighter(query);
      // Para cada item encontrado, recrie o currículo e adiciona na lista
      // de resultados
      for (int i = 0; i < hits.length; ++i) {
        results.add(recuperarDocumento(hits, i, searcher, reader, highlighter));
      }
    } catch (CorruptIndexException e) {
      throw new CurriculumException("Erro ao abrir IndexReader.", e);
    } catch (IOException e) {
      throw new CurriculumException("Erro ao abrir arquivo.", e);
    } catch (InvalidTokenOffsetsException e) {
      throw new CurriculumException("Erro ao destacar os termos no resultado.", e);
    }
    return results;
  }
  
  private Highlighter criarHighlighter(Query query) {
    // QueryScorer para pontuar os termos no resultado
    QueryScorer scorer = new QueryScorer(query);
    // Fragmenter para separar os termos do texto inicial
    // poderia utilizar new SimpleFragmenter()
    Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
    // Formatter para destacar o resultado
    Formatter formatter = new SimpleHTMLFormatter(
        "<span class=\"highlight\">", "</span>");
    // Hightlighter para poder marcar os termos encontrados
    Highlighter highlighter = new Highlighter(formatter, scorer);
    highlighter.setTextFragmenter(fragmenter);
    return highlighter;
  }

  private SearchResult recuperarDocumento(ScoreDoc[] hits, int i, IndexSearcher searcher, 
          IndexReader reader, Highlighter highlighter) throws IOException, InvalidTokenOffsetsException {
    // Obtém o documento a partir de seu ID
    int docId = hits[i].doc;
    Document document = searcher.doc(docId);
    // Recupera o currículo do disco
    File file = new File(this.configBean.getFilesDirectory(), document.get(Constants.FIELD_FILE));
    Curriculum curriculum = loadCurriculum(file);
    // Obtém os tokens do campo conteúdo
    TokenStream tokenStream = TokenSources.getAnyTokenStream(reader, docId,
        Constants.FIELD_CONTENT, this.analyzer);
    // analyzer.tokenStream(field, new
    // StringReader(cv.getContent()));
    // Executa o highlighting e armazena o melhor fragmento
    String bestFragment = highlighter.getBestFragment(tokenStream,
        curriculum.getContent());
    // Cria objeto encapsulando resultado da busca
    SearchResult result = new SearchResult(curriculum, hits[i].score, bestFragment);
    return result;
  }

}
